fix(desktop): open supported Skill directories - #5273
Conversation
Generated-by: Codex
Reject stale project directory actions, preserve unreadable location diagnostics, and keep Storybook location counts consistent with inventory. Regenerate renderer architecture and Astryx inventories to satisfy CI gates. Generated-by: Codex
Record the platform-specific skip in the generated Windows test inventory. Generated-by: Codex
Regenerate renderer architecture and Windows test inventories from the merged source, preserving Skill location context and permissions coverage. Validated with a clean build, typecheck, Biome, Knip, strict inventories, 2709 Desktop tests, 499 UI tests, and Storybook smoke (379 stories / 411 theme renders). Generated-by: Codex
Include upstream Runtime Host PTY integrity and two-client CAS deflakes from 19971f3. Both changed test files pass locally. Generated-by: Codex
Resolve local Skill locations independently of the governance catalog and bind opens to Host-scoped canonical roots. Keep workspace and user locations available when the selected Project disappears, without recreating its path. Generated-by: Codex
Preserve the Skill location scope fix and upstream Composer Session-reference support. Regenerate the shared AppShell token count from the merged source and re-pin the compatible cwd-limit declaration to upstream epoch 163. Generated-by: Codex
Astro-Han
left a comment
There was a problem hiding this comment.
The trust boundary is correctly drawn. The renderer can only echo an opaque ref plus {contextId, createIfMissing}; contextId is sha256(registrationSeed, scope, canonicalRoot, projectId) minted main-side, and open re-derives and compares it (stale_context on any mismatch — project switch, root remount, host re-registration each invalidate). resolveSkillLocation/ensureContainedDirectory re-inspect with lstat + realpath, refuse symlinked leaf directories, and only a canonical contained path ever reaches shell.openPath. No arbitrary-path or traversal route exists from the renderer. The remote-host gate is layered consistently (allowLocalPaths in main and clientPathsAccessible in the renderer), listing derives from the same resolveSkillDiscoveryPaths enumeration the runtime uses, and the open allowlist is a deliberately narrower, fail-closed subset. Controller tests drive the real port shape; main tests pin contextId scoping, project-switch invalidation, canonical-path opening after a root remount, and remote interception. diag:open-skills is retired cleanly with nav:skills covering navigation, and copy is complete across all three locales.
All findings are P3.
- The
'skills'key on the legacyapp:openPathchannel is now dead (open-path-guard.tsstill maps it to{workspaceRoot}/skills,openPathLabels.skills/paths.skills/OpenPathKeyremain in the renderer) — its only caller was removed here, and the dead path is looser than the new channel (leaf symlinks pass realpath-check where the new flow refuses them). Prefer removing it in this PR or noting why it stays. - "Supported location" is encoded in three places that must agree by hand:
resolveSkillDiscoveryPathsentries,skillLocationScope's hardcoded ref list, and theSkillLocationRefunion inmodule-panel-types.ts(with an uncheckedascast atskillLocationRef). A future sixth location would list with anundefinedlabel and refuse open asunknown_location— fail-closed but silently drifting; one ref table or resolving scope from the found entry would collapse it to one declaration. locations[].pathshows the non-canonicalentry.dirwhile open uses the canonical realpath — display-only divergence under symlinked roots.- Small test gaps at the IPC layer:
open_failed(openPath error),create_failed(mkdir EPERM), and leaf-symlinkblocked_pathare pinned only atresolveSkillLocation, not through the handler. - Renderer nits:
stale_context/missingfailures toast but don't auto-refreshSkillLocations;refreshSkillLocationsfires one wasted IPC for remote hosts; location menu items invokerunSkillActionsynchronously where sibling native-window actions defer viarunPageActionAfterMenuClose;openLocationFailures.stale_contextcopy is project-scoped for a reason that can fire on other scopes;skills-panel-locations.test.tsxdoesn't pin thecreateIfMissing: falsearm. - Unrelated churn to trim or annotate: the
external-session-cwd-limit-authority.jsonepoch re-pin (merge bookkeeping — its target file is not in this diff) and whitespace-only deletions insession-inspector/service.tsandconversation-copy.ts.
Retire the legacy Skills open-path key, share standard location identities across Runtime and Desktop, and display canonical opening paths. Refresh invalid location snapshots without retrying mutations, skip remote location reads, defer menu actions, and extend IPC/controller/UI regression coverage. Generated-by: Codex
Preserve the deferred startup refresh while loading locations when asynchronous Project capabilities arrive. Clear stale location snapshots on capability revocation and refresh on restoration without reloading the other Skill projections. Cover capability readiness before and after the startup frame through the Module Hub controller lifecycle. Generated-by: Codex
|
Codex automated follow-up for @Astro-Han's review, implemented in cdca2eb, 00b30ea, and 443605d:
Verification on the updated tree:
The earlier nonblocking UI-attachment gap remains: before/after evidence and the current menu screenshots have been captured locally, but are not attached to this PR yet. No Electron/Windows end-to-end result is claimed. |
Track whether locations were requested independently of the generation fence, which also advances during StrictMode cleanup. Cover at-mount and asynchronous capability readiness under the real StrictMode lifecycle. Generated-by: Codex
Resolve the compatible protocol declaration at main's epoch 165 and retain the reviewed Skill location fixes. Generated-by: Codex
|
Thanks @Astro-Han for reviewing! |
Summary
Desktop exposed one ambiguous Open Skills folder action even though the Runtime catalog discovers five standard directories. When the legacy workspace directory was absent, the action failed with “directory does not exist” instead of opening the location that owns a Skill.
Replace that action with a Skill locations… submenu covering the two project paths, the legacy workspace path, and the two user paths. Existing directories open directly; missing allowlisted directories can be created and opened. The renderer sends only a location ID, and the main process resolves it with containment checks. Remote Runtime Hosts continue to hide local path actions, while individual Skill actions retain their real source path.
Fixes #5272
Verification
npm run typechecknpm run lintnpm run format:checknpx knip --workspace apps/desktopnpx knip --workspace packages/uinpm --workspace @maka/desktop run typecheck:storiesnpm --workspace @maka/desktop run build-storybooknpm --workspace @maka/desktop run smoke:storybook— 359 stories / 388 theme renders passednpm --workspace @maka/ui run test:dist— 453 passedThe root test run also exercised all workspaces. Unrelated existing failures remain in the local environment: Eval's Python tests run under Python 3.9 and cannot parse
X | None, and one storage cross-process test treats Node's SQLite experimental warning as an error.AI use
Tool(s) and scope: Codex implemented and reviewed the Desktop/UI change, tests, localization, and path-containment handling.
Checklist
Does this PR entail a change in behavior?